/*
    This file is part of Raptor.

    Raptor is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Raptor is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Raptor.  If not, see <http://www.gnu.org/licenses/>.

 */

#include "xmlUpdater.h"


updateDialog::updateDialog(Raptor *raptor, QList<S_UPDATEINFO> const& updateInfos)
{
    r = raptor;

    setWindowTitle(tr("Update available"));

    okPushButton = new QPushButton(tr("Add to list"));
    connect(okPushButton, SIGNAL(clicked()), this, SLOT(slot_okPushButton_clicked()));

    treeView = new QTreeView;
    treeView->setMinimumSize(600, 100);

    modelFiles = new QStandardItemModel(0, 5);
    modelFiles->setHeaderData(0, Qt::Horizontal, tr("Download"));
    modelFiles->setHeaderData(1, Qt::Horizontal, tr("Version"));
    modelFiles->setHeaderData(2, Qt::Horizontal, tr("Platform"));
    modelFiles->setHeaderData(3, Qt::Horizontal, tr("Name"));
    modelFiles->setHeaderData(4, Qt::Horizontal, tr("URL"));

    treeView->setModel(modelFiles);
    treeView->setAlternatingRowColors(true);
    treeView->setUniformRowHeights(true);
    treeView->setIconSize(QSize(24, 24));

    QPointer<QGridLayout> layout = new QGridLayout;
    layout->addWidget(treeView, 0, 0, 1, 2);
    layout->addWidget(okPushButton, 1, 1);

    setLayout(layout);

    modelFiles->removeRows(0, modelFiles->rowCount());

    for(int i=0; i<updateInfos.size(); ++i)
    {
        QList<QStandardItem *> listItem;

        //checked
        QStandardItem *item = new QStandardItem("");
        item->setCheckable(true);
        item->setEditable(false);
        item->setCheckState(Qt::Checked);
        listItem.append(item);

        //link
        QStandardItem *itemVersion = new QStandardItem(updateInfos.at(i).version);
        itemVersion->setEditable(false);
        listItem.append(itemVersion);

        //link
        QStandardItem *itemPlatform = new QStandardItem(updateInfos.at(i).platform);
        itemPlatform->setEditable(false);
        listItem.append(itemPlatform);

        //link
        QStandardItem *itemName = new QStandardItem(updateInfos.at(i).name);
        itemName->setEditable(false);
        listItem.append(itemName);

        //link
        QStandardItem *itemUrl = new QStandardItem(updateInfos.at(i).url);
        itemUrl->setEditable(false);
        listItem.append(itemUrl);

        modelFiles->appendRow(listItem);
    //	modelFiles->setData(modelFiles->index(modelFiles->rowCount()-1,0), Qt::Checked, Qt::CheckStateRole);
    }

}

updateDialog::~updateDialog()
{
    if(okPushButton) delete okPushButton;
    if(treeView) delete treeView;
    if(modelFiles) delete modelFiles;
}

void updateDialog::slot_okPushButton_clicked()
{
    if(r)
    {
        for(int i=0; i<modelFiles->rowCount(); ++i)
        {
            if(modelFiles->item(i, 0)->data(Qt::CheckStateRole) ==  Qt::Checked)
            {
                r->addWhenReady("raptor", modelFiles->item(i, 4)->text());
            }
        }        
    }

    done(0);
}

xmlUpdater::xmlUpdater(bool const& useSSL)
{
    timeout = 10;
    dlTimeout = 30;
    maxTries = 3;

    if(useSSL)
        raptorUrl = QUrl("https://ssl-account.com/raptor-loader.org/raptor.php");
    else
        raptorUrl = QUrl("http://raptor-loader.org/raptor.php");

    connect(&http, SIGNAL(requestFinished(int, bool)), this, SLOT(slot_http_requestFinished(int, bool)));
    connect(&http, SIGNAL(stateChanged(int)), this, SLOT(slot_http_stateChanged(int)));
    connect(&http, SIGNAL(readyRead(const QHttpResponseHeader &)), this, SLOT(slot_http_readyRead(const QHttpResponseHeader &)));
    connect(&http, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(slot_http_sslErrors(const QList<QSslError> &)));

    updateLog.clear();
}

xmlUpdater::~xmlUpdater()
{

}

int xmlUpdater::setProxy(QNetworkProxy const& proxy)
{
    return http.setProxy(proxy);
}


void xmlUpdater::setContainerPath(QString const &path)
{
    pathContainer = QDir(path, "*.xml", QDir::IgnoreCase, QDir::Files);
}

void xmlUpdater::setHosterPath(QString const &path)
{
    pathHoster = QDir(path, "*.xml", QDir::IgnoreCase, QDir::Files);
}

void xmlUpdater::updateContainer()
{
  updateXmls(TYPE_CONTAINER);
}

void xmlUpdater::updateHoster()
{
  updateXmls(TYPE_HOSTER);
}

QByteArray xmlUpdater::getPostArray(QString const& action)
{
    QByteArray post = "";

    post.append("action=" + QUrl::toPercentEncoding(action));
    post.append("&version=" + QUrl::toPercentEncoding(QCoreApplication::applicationVersion()));

    return post;
}

void xmlUpdater::updateRaptor(Raptor *raptor)
{
    QByteArray post = getPostArray("checkRaptor");

    //HTTP
    QHttpRequestHeader header("POST", raptorUrl.path());
    header.setValue("Host", raptorUrl.host());
    header.setValue("Content-Type", "application/x-www-form-urlencoded");

    if(raptorUrl.toString().left(5) == "https")
    {
            http.setHost(raptorUrl.host(), QHttp::ConnectionModeHttps);
    } else
    {
            http.setHost(raptorUrl.host());
    }

    state = STATE_WAITING;
    response = QByteArray();

    QDateTime now = QDateTime::currentDateTime();

    waitingId = http.request(header, post);

    while(state == STATE_WAITING)
    {
        qApp->processEvents(QEventLoop::WaitForMoreEvents | QEventLoop::ExcludeUserInputEvents);

        if(now.secsTo(QDateTime::currentDateTime()) >= timeout)
        {
            waitingId = -1;
            http.abort();
            state = STATE_TIMEOUT;
            response = "";
            break;
        }
    }

    if(response == "OK")
    {
        //kein update vorhanden
    } else
    {
        QStringList tmpList = QString(response).trimmed().split("[[:ROW:]]");

        QList<updateDialog::S_UPDATEINFO> updateInfos;

        for(int i=0; i<tmpList.size(); ++i)
        {
            QStringList tmpList2 = tmpList.at(i).split("[[:SEP:]]");

            if(tmpList2.size() == 4)
            {
                updateInfos.append(updateDialog::S_UPDATEINFO(tmpList2.at(0), tmpList2.at(1), tmpList2.at(2), tmpList2.at(3)));
            }
        }

        if(updateInfos.size() > 0)
        {
            updateDialog updDialog(raptor, updateInfos);
            updDialog.exec();
        } else
        {
            //komisch..
        }
    }
}

void xmlUpdater::updateXmls(xmlUpdater::E_TYPE const type)
{
    if((type == TYPE_HOSTER && pathHoster.exists()) || (type == TYPE_CONTAINER && pathContainer.exists()))
    {
        QFileInfoList list;

        if(type == TYPE_HOSTER)
          list = pathHoster.entryInfoList();
        else
          list = pathContainer.entryInfoList();

        bool unstrangeAll = false;
        bool unstrangeNothing = false;
        bool updateAll = false;
        bool updateNothing = false;
        bool deleteAll = false;
        bool keepAll = false;

	for(int i=0; i<list.size(); ++i)
	{
	    QFileInfo fileInfo = list.at(i);
	    QString reply = "";
            E_FILESTATE state = checkFile(fileInfo.absoluteFilePath(), type, reply);

	    switch(state)
	    {
		case STATE_OK:
                {
                    break;
                }

		case STATE_TIMEOUT:
                {
                    qDebug() << "Connection to raptor-loader timed out.";
                    i = list.size();
                    break;
                }

		default:
		case STATE_BAD:
                {
                    if(keepAll) break;

                    if(deleteAll)
                    {
                        QFile(fileInfo.absoluteFilePath()).remove();
                        break;
                    }

                    QMessageBox msgBox;
		    if(type == TYPE_HOSTER)
			msgBox.setText(tr("The hoster file: %1 is not certified by raptor-loader.org. Using it could be dangerous !!").arg(fileInfo.fileName()));
		    else
			msgBox.setText(tr("The container file: %1 is not certified by raptor-loader.org. Using it could be dangerous !!").arg(fileInfo.fileName()));

                    msgBox.setInformativeText(tr("Do you want to delete this file ?"));
                    msgBox.setDetailedText(reply);
                    msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll);
                    msgBox.setDefaultButton(QMessageBox::Yes);

                    int ret = msgBox.exec();

                    if(ret == QMessageBox::YesToAll) deleteAll = true;
                    if(ret == QMessageBox::NoToAll) keepAll = true;

                    if(ret == QMessageBox::Yes || ret == QMessageBox::YesToAll) QFile(fileInfo.absoluteFilePath()).remove();

                    break;
                }

		case STATE_UPDATE:
                {
                    if(updateNothing) break;

                    if(updateAll)
                    {
                        if(updateXml(fileInfo, type))
                            --i;
                        else
			    qDebug() << tr("Error while updating xml file");

                        break;
                    }

                    QMessageBox msgBox;
		    if(type == TYPE_HOSTER)
			msgBox.setText(tr("A newer version for the hoster file: %1 is available !!").arg(fileInfo.fileName()));
		    else
			msgBox.setText(tr("A newer version for the container file: %1 is available !!").arg(fileInfo.fileName()));

                    msgBox.setInformativeText(tr("Do you want to update to this version ?"));
                    msgBox.setDetailedText(reply);
                    msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll);
                    msgBox.setDefaultButton(QMessageBox::Yes);

                    int ret = msgBox.exec();

                    if(ret == QMessageBox::YesToAll) updateAll = true;
                    if(ret == QMessageBox::NoToAll) updateNothing = true;

                    if(ret == QMessageBox::Yes || ret == QMessageBox::YesToAll)
                    {
                        if(updateXml(fileInfo, type))
                            --i;
                        else
			    qDebug() << tr("Error while updating xml file");
                    }

                    break;
                }

                case STATE_STRANGE:
                {
                    if(unstrangeNothing) break;

                    if(unstrangeAll)
                    {
                        if(updateXml(fileInfo, type))
                            --i;
                        else
			    qDebug() << tr("Error while updating xml file");

                        break;
                    }

                    QMessageBox msgBox;
		    if(type == TYPE_HOSTER)
			msgBox.setText(tr("The version of your hoster file: %1 is not listed in the online database !!").arg(fileInfo.fileName()));
		    else
			msgBox.setText(tr("The version of your container file: %1 is not listed in the online database !!").arg(fileInfo.fileName()));

                    msgBox.setInformativeText(tr("Do you want to update to an official version ?"));
                    msgBox.setDetailedText(reply);
                    msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll);
                    msgBox.setDefaultButton(QMessageBox::Yes);

                    int ret = msgBox.exec();

                    if(ret == QMessageBox::YesToAll) unstrangeAll = true;
                    if(ret == QMessageBox::NoToAll) unstrangeNothing = true;

                    if(ret == QMessageBox::Yes || ret == QMessageBox::YesToAll)
                    {
                        if(updateXml(fileInfo, type))
                            --i;
                        else
			    qDebug() << tr("Error while updating xml file");
                    }

                    break;
                }
	    }
	}
    }
}

xmlUpdater::E_FILESTATE xmlUpdater::checkFile(QString const& file, xmlUpdater::E_TYPE const type, QString &reply)
{
    QFileInfo fileInfo = QFileInfo(file);
            
    QByteArray bytePath = fileInfo.absoluteFilePath().toAscii();
    QByteArray post = getPostArray("checkFile");

    if(type == TYPE_HOSTER)
	post.append("&type=" + QUrl::toPercentEncoding("hoster"));
    else
	post.append("&type=" + QUrl::toPercentEncoding("container"));

    post.append("&size=" + QUrl::toPercentEncoding(QString("%1").arg(fileInfo.size())));
    post.append("&fileName=" + QUrl::toPercentEncoding(fileInfo.fileName()));

    char *path = bytePath.data();

    unsigned char output[16];

    if(md5_file(path, output) > 0)
    {
	qDebug() << "MD5 failed" << path;
	post.append("&md5=" + QUrl::toPercentEncoding("failed"));
    } else
    {
	QString tmpStr = "";

	for(int i=0; i<16; ++i)
	{
	    QString tmpHex = QString("%1").arg(output[i], 0, 16);
	    tmpStr += (tmpHex.length() == 1 ? "0": "") + tmpHex;
	}

	post.append("&md5=" + QUrl::toPercentEncoding(tmpStr));
    }

    unsigned char output2[32];

    if(sha2_file(path, output2, 0) > 0)
    {
	qDebug() << "SHA256 failed" << path;
	post.append("&sha256=" + QUrl::toPercentEncoding("failed"));
    } else
    {
	QString tmpStr = "";

	for(int i=0; i<32; ++i)
	{
	    QString tmpHex = QString("%1").arg(output2[i], 0, 16);
	    tmpStr += (tmpHex.length() == 1 ? "0": "") + tmpHex;
	}
	post.append("&sha256=" + QUrl::toPercentEncoding(tmpStr));
    }

    //HTTP
    QHttpRequestHeader header("POST", raptorUrl.path());
    header.setValue("Host", raptorUrl.host());
    header.setValue("Content-Type", "application/x-www-form-urlencoded");
				
    if(raptorUrl.toString().left(5) == "https")
    {
            http.setHost(raptorUrl.host(), QHttp::ConnectionModeHttps);
    } else
    {
            http.setHost(raptorUrl.host());
    }

    state = STATE_WAITING;
    response = QByteArray();

    QDateTime now = QDateTime::currentDateTime();

    waitingId = http.request(header, post);    

    while(state == STATE_WAITING)
    {
	qApp->processEvents(QEventLoop::WaitForMoreEvents | QEventLoop::ExcludeUserInputEvents);

	if(now.secsTo(QDateTime::currentDateTime()) >= timeout)
	{
	    waitingId = -1;
	    http.abort();
	    state = STATE_TIMEOUT;
	    response = "";
	    break;
	}
    }

    if(response == "BAD")
    {
        reply = tr("File could not be found in Database.");
	state = STATE_BAD;
    } else if(response == "GOOD")
    {
	state = STATE_OK;
    } else if(response == "UPDATE")
    {
        reply = tr("A newer file was found in the online Database.");
	state = STATE_UPDATE;
    } else if(response == "STRANGE")
    {
        reply = tr("Your file is not listed in the online Database. If you don't update, this could be dangerous !!");
        state = STATE_STRANGE;
    } else
    {
	reply = response;
	state = STATE_ERROR;
    }

    return state;
}

bool xmlUpdater::updateXml(QFileInfo const& fileInfo, xmlUpdater::E_TYPE const& type)
{
    emit msg(tr("Updating file: ") + fileInfo.fileName());

    if(http.state() != QHttp::Unconnected && http.state() != QHttp::Connected)
    {
        qDebug() << "qhttp still working, state:" << http.state();
        return false;
    }

    int tries = updateLog.value(fileInfo.fileName(), 0);

    if(tries >= maxTries)
    {
        qDebug() << ">maxtries";
        return false;
    }

    QFile file(fileInfo.absoluteFilePath());

    if(file.open(QIODevice::WriteOnly))
    {
        ++tries;
        updateLog.insert(fileInfo.fileName(), tries);

        QHttpRequestHeader header("POST", raptorUrl.path());
        header.setValue("Host", raptorUrl.host());
        header.setValue("Content-Type", "application/x-www-form-urlencoded");

        if(raptorUrl.toString().left(5) == "https")
        {
                http.setHost(raptorUrl.host(), QHttp::ConnectionModeHttps);
        } else
        {
                http.setHost(raptorUrl.host());
        }

        QByteArray post = getPostArray("getFile");
        
        if(type == TYPE_HOSTER)
          post.append("&type=" + QUrl::toPercentEncoding("hoster"));
        else
          post.append("&type=" + QUrl::toPercentEncoding("container"));

        post.append("&fileName=" + QUrl::toPercentEncoding(fileInfo.fileName()));

        state = STATE_WAITING;

        QDateTime now = QDateTime::currentDateTime();

        waitingId = http.request(header, post, &file);

        while(state == STATE_WAITING)
        {
            qApp->processEvents(QEventLoop::WaitForMoreEvents | QEventLoop::ExcludeUserInputEvents);

            if(now.secsTo(QDateTime::currentDateTime()) >= dlTimeout)
            {
                waitingId = -1;
                http.abort();
                file.close();
                file.remove();
                state = STATE_TIMEOUT;

                qDebug() << "timeout";
                return false;
            }
        }

        file.close();

        return true;
    } else
    {
        qDebug() << "cant open";
        return false;
    }

    qDebug() << "error unknown";
    return false;
}

void xmlUpdater::slot_http_requestFinished(int requestId, bool httpError)
{
    //nur reagieren, wenn auch beabsichtigt
    if(waitingId != requestId) return;

    if(httpError)
    {
      response = http.errorString().toAscii();
      state = STATE_ERROR;
    } else
    {
        state = STATE_DONE;

        /*if(http.state() == QHttp::Unconnected)
            state = STATE_DONE;
        else
            waitingId = http.close();*/
    }
}

void xmlUpdater::slot_http_stateChanged(int _state)
{
    if(state == STATE_WAITING && _state == QHttp::Unconnected)
        state = STATE_DONE;
}

void xmlUpdater::slot_http_sslErrors(const QList<QSslError> &errors)
{
    Q_UNUSED(errors);
    http.ignoreSslErrors();
}

void xmlUpdater::slot_http_readyRead(const QHttpResponseHeader & resp)
{
    Q_UNUSED(resp);
    response.append(http.readAll());
}
